package me.itsoo.artemis.framework.repository.mapper.condition;

import me.itsoo.artemis.framework.core.tuple.SimplePair;
import me.itsoo.artemis.framework.core.util.Assert;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;

/**
 * QueryCondition
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/11
 */
public class QueryCondition extends AbstractCondition {

    private static final Object NOT_SET = new Object();

    private final String operate;

    private final String field;

    private final List<QueryCondition> chain;

    private final SimplePair<String, Object> cond = new SimplePair<>();

    private Object isValue = NOT_SET;

    /**
     * 首节点构造
     *
     * @param operate String
     * @param field   String
     */
    private QueryCondition(String operate, String field) {
        this.chain = new ArrayList<>();
        this.chain.add(this);
        this.operate = operate;
        this.field = field;
    }

    /**
     * 连接节点构造
     *
     * @param chain   List
     * @param operate String
     * @param field   String
     */
    private QueryCondition(List<QueryCondition> chain, String operate, String field) {
        this.chain = chain;
        this.chain.add(this);
        this.operate = operate;
        this.field = field;
    }

    /**
     * where
     *
     * @param field String
     * @return QueryCondition
     */
    public static QueryCondition where(String field) {
        Assert.isNotBlank(field, "'field' cannot be empty");
        return new QueryCondition("where", field);
    }

    /**
     * and
     *
     * @param field String
     * @return QueryCondition
     */
    public QueryCondition and(String field) {
        Assert.isNotBlank(field, "'field' cannot be empty");
        return new QueryCondition(this.chain, "and", field);
    }

    /**
     * or
     *
     * @param field String
     * @return QueryCondition
     */
    public QueryCondition or(String field) {
        Assert.isNotBlank(field, "'field' cannot be empty");
        return new QueryCondition(this.chain, "or", field);
    }

    /**
     * or
     *
     * @return QueryCondition
     */
    public QueryCondition or() {
        final QueryCondition result = new QueryCondition(this.chain, "or", null);
        result.isValue = NOT_SET;
        return result;
    }

    /**
     * groupStart
     *
     * @param field String
     * @return QueryCondition
     */
    public QueryCondition groupStart(String field) {
        final QueryCondition result = new QueryCondition(this.chain, "(", field);
        result.isValue = NOT_SET;
        return result;
    }

    /**
     * groupEnd
     *
     * @return QueryCondition
     */
    public QueryCondition groupEnd() {
        final QueryCondition result = new QueryCondition(this.chain, ")", null);
        result.isValue = NOT_SET;
        return result;
    }

    /**
     * isNull
     *
     * @return QueryCondition
     */
    public QueryCondition isNull() {
        cond.setKey("is");
        cond.setValue(null);
        isValue = null;
        return this;
    }

    /**
     * notNull
     *
     * @return QueryCondition
     */
    public QueryCondition notNull() {
        cond.setKey("is not");
        cond.setValue(null);
        isValue = null;
        return this;
    }

    /**
     * like
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition like(Object value) {
        cond.setKey("like");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * eq
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition eq(Object value) {
        cond.setKey("=");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * ne
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition ne(Object value) {
        cond.setKey("<>");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * lt
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition lt(Object value) {
        cond.setKey("<");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * lte
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition lte(Object value) {
        cond.setKey("<=");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * gt
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition gt(Object value) {
        cond.setKey(">");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * gte
     *
     * @param value Object
     * @return QueryCondition
     */
    public QueryCondition gte(Object value) {
        cond.setKey(">=");
        cond.setValue(value);
        isValue = value;
        return this;
    }

    /**
     * in
     *
     * @param values Collection
     * @return QueryCondition
     */
    public QueryCondition in(Collection<?> values) {
        cond.setKey("in");
        cond.setValue(values);
        isValue = values;
        return this;
    }

    /**
     * in
     *
     * @param values Object[]
     * @return QueryCondition
     */
    public QueryCondition in(Object... values) {
        return in(Arrays.asList(values));
    }

    /**
     * nin
     *
     * @param values Collection
     * @return QueryCondition
     */
    public QueryCondition nin(Collection<?> values) {
        cond.setKey("not in");
        cond.setValue(values);
        isValue = values;
        return this;
    }

    /**
     * nin
     *
     * @param values Object[]
     * @return QueryCondition
     */
    public QueryCondition nin(Object... values) {
        return nin(Arrays.asList(values));
    }

    @Override
    public String getConditionCommand() {
        final StringJoiner result = new StringJoiner(" ");

        for (QueryCondition qc : chain) {
            result.add(qc.operate);

            if (qc.isValue != NOT_SET) {
                result.add(qc.field.trim());
                result.add(qc.cond.getKey());
                final Object value = qc.cond.getValue();

                if (value instanceof Collection) {
                    result.add("(");

                    final Collection<?> coll = (Collection<?>) value;
                    for (int i = 0, len = coll.size(), last = len - 1; i < len; i++) {
                        String sp = "";
                        if (i < last) {
                            sp = ",";
                        }
                        result.add("?".concat(sp));
                    }

                    result.add(")");
                } else {
                    result.add("?");
                }
            }
        }

        return result.toString();
    }

    @Override
    public Object[] getConditionArgs() {
        final List<Object> result = new ArrayList<>(chain.size());

        for (QueryCondition qc : chain) {
            if (qc.isValue != NOT_SET) {
                final Object value = qc.cond.getValue();

                if (value instanceof Collection) {
                    final Collection<?> coll = (Collection<?>) value;
                    result.addAll(coll);
                } else {
                    result.add(value);
                }
            }
        }

        return result.toArray();
    }
}
